#pragma once
namespace NEngine {

// -- forward declaration
struct Vector4;
typedef unsigned short ushort;
typedef unsigned int uint;

union BitComparison {
    float realpart;
    int intpart;
};

enum SigBit {
    eMostSig = (1 << 31), 
};

// ================================================================================================
// Basic mathematic implementation of Matrix4x4
// ================================================================================================
struct Matrix4x4 {
    
    union {
        struct {
            float m00, m01, m02, m03;
            float m10, m11, m12, m13;
            float m20, m21, m22, m23;
            float m30, m31, m32, m33;
        };
        float m[4][4];
    };

    // -- default constructor
    Matrix4x4();

    // -- create a rotation matrix
    Matrix4x4(float, float, float,
              float, float, float,
              float, float, float);
    
    // -- create a 3x3 matrix with trans vec with 15 floats
    Matrix4x4(float, float, float,
              float, float, float,
              float, float, float,
              float, float, float);

    // -- create a 4x4 matrix with 16 floats
    Matrix4x4(float, float, float, float,
              float, float, float, float,
              float, float, float, float,
              float, float, float, float);

    // -- create a 4x4 matrix with 4 row vectors
    Matrix4x4(Vector4*, Vector4*, Vector4*, Vector4*);

    // -- public interface
    Matrix4x4 Transpose3x3();
    Matrix4x4 Transpose4x4() const;
    Matrix4x4 Cofactor() const;
    Matrix4x4 Adjoint();
    Matrix4x4 Inverse();
    Matrix4x4 InvTrans() const;
    float Determinant2x2(float, float, float, float) const;
    float Determinant3x3() const;

    // -- mutator methods
    void SetRow1(const Vector4&);
    void SetRow1(float, float, float);
    void SetRow2(const Vector4&);
    void SetRow2(float, float, float);
    void SetRow3(const Vector4&);
    void SetRow3(float, float, float);
    void SetRow4(const Vector4&);
    void SetRow4(float, float, float);

    // -- accessor methods
    Vector4& GetRow1();
    const Vector4& GetRow1() const;
    Vector4& GetRow2();
    const Vector4& GetRow2() const;
    Vector4& GetRow3();
    const Vector4& GetRow3() const;
    Vector4& GetRow4();
    const Vector4& GetRow4() const;
    Matrix4x4 GetRotation() const;
    Matrix4x4 GetTranslation() const;

    // -- operator overloads
    Matrix4x4 operator*(float s);
    Matrix4x4 operator*=(float s);
    Matrix4x4 operator*(Matrix4x4& m);
    Matrix4x4 operator*=(Matrix4x4& m);
    Matrix4x4 operator+(Matrix4x4& m);
    Matrix4x4 operator+=(Matrix4x4& m);
};

// ================================================================================================
// Default Constructor
// ================================================================================================
inline Matrix4x4::Matrix4x4() {
    m00 = 0.0f;
    m01 = 0.0f;
    m02 = 0.0f;
    m03 = 0.0f;
    m10 = 0.0f;
    m11 = 0.0f;
    m12 = 0.0f;
    m13 = 0.0f;
    m20 = 0.0f;
    m21 = 0.0f;
    m22 = 0.0f;
    m23 = 0.0f;
    m30 = 0.0f;
    m31 = 0.0f;
    m32 = 0.0f;
    m33 = 0.0f;
}

inline Matrix4x4::Matrix4x4(float m00_, float m01_, float m02_,
                            float m10_, float m11_, float m12_,
              			    float m20_, float m21_, float m22_) {
    m00 = m00_;
    m01 = m01_;
    m02 = m02_;
    m03 = 0.0f;
    m10 = m10_;
    m11 = m11_;
    m12 = m12_;
    m13 = 0.0f;
    m20 = m20_;
    m21 = m21_;
    m22 = m22_;
    m23 = 0.0f;
    m30 = 0.0f; 
    m31 = 0.0f;
    m32 = 0.0f;
    m33 = 1.0f;
}

inline Matrix4x4::Matrix4x4(float m00_, float m01_, float m02_,
                            float m10_, float m11_, float m12_,
              			    float m20_, float m21_, float m22_,
                            float m30_, float m31_, float m32_) {
    m00 = m00_;
    m01 = m01_;
    m02 = m02_;
    m03 = 0.0f;
    m10 = m10_;
    m11 = m11_;
    m12 = m12_;
    m13 = 0.0f;
    m20 = m20_;
    m21 = m21_;
    m22 = m22_;
    m23 = 0.0f;
    m30 = m30_; 
    m31 = m31_;
    m32 = m32_;
    m33 = 1.0f;
}

inline Matrix4x4::Matrix4x4(float m00_, float m01_, float m02_, float m03_,
                            float m10_, float m11_, float m12_, float m13_,
              			    float m20_, float m21_, float m22_, float m23_,
              			    float m30_, float m31_, float m32_, float m33_) {
    m00 = m00_;
    m01 = m01_;
    m02 = m02_;
    m03 = m03_;
    m10 = m10_;
    m11 = m11_;
    m12 = m12_;
    m13 = m13_;
    m20 = m20_;
    m21 = m21_;
    m22 = m22_;
    m23 = m23_;
    m30 = m30_;
    m31 = m31_;
    m32 = m32_;
    m33 = m33_;
}

// ================================================================================================
// Mutator methods
// ================================================================================================
inline void Matrix4x4::SetRow1(float x, float y, float z) { 
    m00 = x;
    m01 = y;
    m02 = z;
}

inline void Matrix4x4::SetRow2(float x, float y, float z) { 
    m00 = x;
    m01 = y;
    m02 = z;
}

inline void Matrix4x4::SetRow3(float x, float y, float z) { 
    m00 = x;
    m01 = y;
    m02 = z;
}

inline void Matrix4x4::SetRow4(float x, float y, float z) { 
    m00 = x;
    m01 = y;
    m02 = z;
}

// ================================================================================================
// Return a simple 4x4 identity matrix
// ================================================================================================
inline Matrix4x4 Identity() { 
    Matrix4x4 temp;

    temp.m00 = 1.0f;
    temp.m11 = 1.0f;
    temp.m22 = 1.0f;
    temp.m33 = 1.0f;

    return temp;
}

// ================================================================================================
// Accessor methods
// ================================================================================================
inline Vector4& Matrix4x4::GetRow1() {
    return *reinterpret_cast<Vector4*>(&m00);
}

inline const Vector4& Matrix4x4::GetRow1() const {
    return *reinterpret_cast<const Vector4*>(&m00);
}

inline Vector4& Matrix4x4::GetRow2() {
    return *reinterpret_cast<Vector4*>(&m10);
}

inline const Vector4& Matrix4x4::GetRow2() const {
    return *reinterpret_cast<const Vector4*>(&m10);
}

inline Vector4& Matrix4x4::GetRow3() {
    return *reinterpret_cast<Vector4*>(&m20);
}

inline const Vector4& Matrix4x4::GetRow3() const {
    return *reinterpret_cast<const Vector4*>(&m20);
}

inline Vector4& Matrix4x4::GetRow4() {
    return *reinterpret_cast<Vector4*>(&m30);
}

inline const Vector4& Matrix4x4::GetRow4() const {
    return *reinterpret_cast<const Vector4*>(&m30);
}

inline Matrix4x4 Matrix4x4::GetRotation() const {
    Matrix4x4 tmat(m00, m01, m02,
                   m10, m11, m12,
                   m20, m21, m22);
    return tmat;
}

inline Matrix4x4 Matrix4x4::GetTranslation() const {
    Matrix4x4 tmat = Identity();
    tmat.m30 = m30;
    tmat.m31 = m31;
    tmat.m32 = m32;

    return tmat;
}

// ================================================================================================
// Overloaded operator for scalar and matrix multiplication, for symmetry
// ================================================================================================
Matrix4x4 operator*(float s, Matrix4x4& mat);
Matrix4x4 operator*=(float s, Matrix4x4& mat);

// ================================================================================================
// Vector class in homogenous coordinates
// ================================================================================================
struct Vector4 {
    union {
        struct {
            float x;
            float y;
            float z;            
            float w;
        };
        float pt[4];
    };

    // -- default constructor
    Vector4();

    // -- lifetime
    Vector4(float x, float y, float z);

    // -- operator overloads
    float& operator[](uint);
    float const& operator[](uint) const;
    Vector4 operator+(const Vector4& vec);
    Vector4 operator+(const Vector4& vec) const;
    Vector4 operator+=(Vector4& vec);
    Vector4 operator+=(const Vector4& vec);
    Vector4 operator-(const Vector4& vec);
    Vector4 operator-(const Vector4& vec) const;
    Vector4 operator-=(Vector4& vec);
    Vector4 operator-=(const Vector4& vec);
    Vector4 operator*(const Matrix4x4& mat);
    Vector4 operator*(const Matrix4x4& mat) const;
    Vector4 operator*=(Matrix4x4& mat);
    Vector4 operator*=(const Matrix4x4& mat);
    Vector4 operator*(const float num);
    Vector4 operator*(const float num) const;
    Vector4 operator*=(float num);

    // -- negation operator
    Vector4 operator-() const;
};

// ================================================================================================
// Constructors
// ================================================================================================
inline Vector4::Vector4() : x(0.0f), y(0.0f), z(0.0f), w(1.0f) {
}

inline Vector4::Vector4(float x_, float y_, float z_) : x(x_), y(y_), z(z_), w(1.0f) {
}

// ================================================================================================
// Operator overloads
// ================================================================================================
inline float& Vector4::operator[](uint index) {
    return pt[index];
}

inline float const& Vector4::operator[](uint index) const {
    return pt[index];
}

inline Vector4 Vector4::operator+(const Vector4& vec) {
    Vector4 temp;

    temp.x = x + vec.x;
    temp.y = y + vec.y;
    temp.z = z + vec.z;

    return temp;
}

inline Vector4 Vector4::operator+(const Vector4& vec) const {
    Vector4 temp;

    temp.x = x + vec.x;
    temp.y = y + vec.y;
    temp.z = z + vec.z;

    return temp;
}

inline Vector4 Vector4::operator+=(Vector4& vec) {
    Vector4 temp;

    temp.x = x + vec.x;
    temp.y = y + vec.y;
    temp.z = z + vec.z;

    *this = temp;

    return temp;
}

inline Vector4 Vector4::operator+=(const Vector4& vec) {
    Vector4 temp;

    temp.x = x + vec.x;
    temp.y = y + vec.y;
    temp.z = z + vec.z;

    *this = temp;

    return temp;
}

inline Vector4 Vector4::operator-(const Vector4& vec) {
    Vector4 temp;

    temp.x = x - vec.x;
    temp.y = y - vec.y;
    temp.z = z - vec.z;

    return temp;
}

inline Vector4 Vector4::operator-(const Vector4& vec) const{
    Vector4 temp;

    temp.x = x - vec.x;
    temp.y = y - vec.y;
    temp.z = z - vec.z;

    return temp;
}

inline Vector4 Vector4::operator-=(Vector4& vec) {
    Vector4 temp;

    temp.x = x - vec.x;
    temp.y = y - vec.y;
    temp.z = z - vec.z;

    *this = temp;

    return temp;
}

inline Vector4 Vector4::operator*(const Matrix4x4& mat) {
    Vector4 temp;

    temp.x = x*mat.m00 + y*mat.m10 + z*mat.m20 + w*mat.m30; 
    temp.y = x*mat.m01 + y*mat.m11 + z*mat.m21 + w*mat.m31; 
    temp.z = x*mat.m02 + y*mat.m12 + z*mat.m22 + w*mat.m32; 
    temp.w = x*mat.m03 + y*mat.m13 + z*mat.m23 + w*mat.m33; 

    return temp;
}

inline Vector4 Vector4::operator*(const Matrix4x4& mat) const {
    Vector4 temp;

    temp.x = x*mat.m00 + y*mat.m10 + z*mat.m20 + w*mat.m30; 
    temp.y = x*mat.m01 + y*mat.m11 + z*mat.m21 + w*mat.m31; 
    temp.z = x*mat.m02 + y*mat.m12 + z*mat.m22 + w*mat.m32; 
    temp.w = x*mat.m03 + y*mat.m13 + z*mat.m23 + w*mat.m33; 

    return temp;
}

inline Vector4 Vector4::operator*=(Matrix4x4& mat) {
    Vector4 temp;

    temp.x = x*mat.m00 + y*mat.m10 + z*mat.m20 + w*mat.m30; 
    temp.y = x*mat.m01 + y*mat.m11 + z*mat.m21 + w*mat.m31; 
    temp.z = x*mat.m02 + y*mat.m12 + z*mat.m22 + w*mat.m32; 
    temp.w = x*mat.m03 + y*mat.m13 + z*mat.m23 + w*mat.m33; 

    *this = temp;

    return temp;
}

inline Vector4 Vector4::operator*=(const Matrix4x4& mat) {
    Vector4 temp;

    temp.x = x*mat.m00 + y*mat.m10 + z*mat.m20 + w*mat.m30; 
    temp.y = x*mat.m01 + y*mat.m11 + z*mat.m21 + w*mat.m31; 
    temp.z = x*mat.m02 + y*mat.m12 + z*mat.m22 + w*mat.m32; 
    temp.w = x*mat.m03 + y*mat.m13 + z*mat.m23 + w*mat.m33; 

    *this = temp;

    return temp;
}

inline Vector4 Vector4::operator*(float num) {
    Vector4 temp;

    temp.x = x * num;
    temp.y = y * num;
    temp.z = z * num;

    return temp;
}

inline Vector4 Vector4::operator*(float num) const {
    Vector4 temp;

    temp.x = x * num;
    temp.y = y * num;
    temp.z = z * num;

    return temp;
}

inline Vector4 Vector4::operator*=(float num) {
    Vector4 temp;

    temp.x = x * num;
    temp.y = y * num;
    temp.z = z * num;

    *this = temp;

    return temp;
}

// ================================================================================================
// Negation operator
// ================================================================================================
inline Vector4 Vector4::operator-() const {
    return Vector4(-x, -y, -z);
}

// ================================================================================================
// Scalar and vector multiplication these are for symmetry
// ================================================================================================
inline Vector4 operator*(float num,const Vector4& vec) {
    Vector4 temp;

    temp.x = vec.x * num;
    temp.y = vec.y * num;
    temp.z = vec.z * num;

    return temp;
}

inline Vector4 operator*=(float num, Vector4& vec) {
    Vector4 temp;

    temp.x = vec.x * num;
    temp.y = vec.y * num;
    temp.z = vec.z * num;

    vec = temp;

    return temp;
}

} // NEngine namespace
